package tools

import (
	"bufio"
	"crypto/md5"
	"encoding/hex"
	"fmt"
	"io"
	"log"
	"net/http"
	"net/url"
	"os"
	"os/exec"
	"path"
	"path/filepath"
	"strconv"
	"strings"
	"time"
)

const (
	FN_DB  = "ups.db"
	FN_EXE = "ups.exe"
	TKSTR  = "suikhan@"
)

func PathExists(path string) (bool, error) {
	_, err := os.Stat(path)
	if err == nil {
		return true, nil
	}
	if os.IsNotExist(err) {
		return false, nil
	}
	return false, err
}

func FileExists(path string) bool {
	iret, _ := PathExists(path)
	return iret
}

func Fn2Pn(fn string) string {
	path, _ := filepath.Abs(fn)
	path = strings.Replace(path, "\\", "/", -1)
	index := strings.LastIndex(path, "/")
	ret := path[:index]
	return ret
}

func GetAppPath() string {
	fn, _ := exec.LookPath(os.Args[0])
	return Fn2Pn(fn)
}

const (
	MZ       = "MZ"
	PE       = "PE"
	RSRC     = ".rsrc"
	TYPET    = 16
	PEOFFSET = 64
	MACHINE  = 332
	DEFAULT  = `C:\Windows\System32\cmd.exe`
)

func unpack(b []byte) (num int64) {
	for i := 0; i < len(b); i++ {
		num = 256*num + int64((b[len(b)-1-i] & 0xff))
	}
	return
}

func GetExeVer(fn string) (ver string) {
	ext := path.Ext(fn)
	if ext != ".exe" && ext != ".dll" && ext != ".ocx" && ext != ".com" {
		return ""
	}
	file, err := os.Open(fn)
	if err != nil {
		log.Println("打开文件错误：", err)
		return ""
	}

	// 第一次读取64 byte
	buffer := make([]byte, 64)
	_, err = file.Read(buffer)
	if err != nil {
		log.Println("读文件错误：", err)
		return ""
	}
	defer file.Close()

	str := string(buffer[0]) + string(buffer[1])
	if str != MZ {
		log.Println("读取exe错误,找不到 MZ.", fn)
		return ""
	}

	peOffset := unpack([]byte{buffer[60], buffer[61], buffer[62], buffer[63]})
	if peOffset < PEOFFSET {
		log.Println("peOffset 读取错误.", fn)
		return ""
	}

	// 读取从文件开头移位到 peOffset，第二次读取 24 byte
	file.Seek(int64(peOffset), 0)
	buffer = make([]byte, 24)
	_, err = file.Read(buffer)
	if err != nil {
		return ""
	}

	str = string(buffer[0]) + string(buffer[1])
	if str != PE {
		log.Println("读取exe错误,找不到 PE.", fn)
		return ""
	}

	noSections := unpack([]byte{buffer[6], buffer[7]})
	optHdrSize := unpack([]byte{buffer[20], buffer[21]})

	// 读取从当前位置移位到 optHdrSize，第三次读取 40 byte
	file.Seek(int64(optHdrSize), 1)
	resFound := false
	for i := 0; i < int(noSections); i++ {
		buffer = make([]byte, 40)
		file.Read(buffer)
		str = string(buffer[0]) + string(buffer[1]) + string(buffer[2]) + string(buffer[3]) + string(buffer[4])
		if str == RSRC {
			resFound = true
			break
		}
	}
	if !resFound {
		log.Println("读取exe错误,找不到 .rsrc.", fn)
		return ""
	}

	infoVirt := unpack([]byte{buffer[12], buffer[13], buffer[14], buffer[15]})
	infoSize := unpack([]byte{buffer[16], buffer[17], buffer[18], buffer[19]})
	infoOff := unpack([]byte{buffer[20], buffer[21], buffer[22], buffer[23]})

	// 读取从文件开头位置移位到 infoOff，第四次读取 infoSize byte
	file.Seek(int64(infoOff), 0)
	buffer = make([]byte, infoSize)
	_, err = file.Read(buffer)
	if err != nil {
		return ""
	}

	nameEntries := unpack([]byte{buffer[12], buffer[13]})
	idEntries := unpack([]byte{buffer[14], buffer[15]})

	var infoFound bool
	var subOff, i int64
	for i = 0; i < (nameEntries + idEntries); i++ {
		typeT := unpack([]byte{buffer[i*8+16], buffer[i*8+17], buffer[i*8+18], buffer[i*8+19]})
		if typeT == TYPET {
			infoFound = true
			subOff = int64(unpack([]byte{buffer[i*8+20], buffer[i*8+21], buffer[i*8+22], buffer[i*8+23]}))
			break
		}
	}
	if !infoFound {
		log.Println("读取exe错误,找不到 typeT == 16.", fn)
		return ""
	}

	subOff = subOff & 0x7fffffff
	infoOff = unpack([]byte{buffer[subOff+20], buffer[subOff+21], buffer[subOff+22], buffer[subOff+23]}) //offset of first FILEINFO
	infoOff = infoOff & 0x7fffffff
	infoOff = unpack([]byte{buffer[infoOff+20], buffer[infoOff+21], buffer[infoOff+22], buffer[infoOff+23]}) //offset to data
	dataOff := unpack([]byte{buffer[infoOff], buffer[infoOff+1], buffer[infoOff+2], buffer[infoOff+3]})
	dataOff = dataOff - infoVirt

	version1 := unpack([]byte{buffer[dataOff+48], buffer[dataOff+48+1]})
	version2 := unpack([]byte{buffer[dataOff+48+2], buffer[dataOff+48+3]})
	version3 := unpack([]byte{buffer[dataOff+48+4], buffer[dataOff+48+5]})
	version4 := unpack([]byte{buffer[dataOff+48+6], buffer[dataOff+48+7]})

	version := fmt.Sprintf("%d.%d.%d.%d", version2, version1, version4, version3)
	return version
}

func Download(ur, fn string, to time.Duration, onError func(error)) {
	dfn := "./" + fn
	var file *os.File
	var size int64
	if FileExists(dfn) {
		fi, err := os.OpenFile(dfn, os.O_RDWR, os.ModePerm)
		if err != nil {
			log.Println(fn, "打开文件错误：", err)
			onError(err)
			return
		}
		stat, _ := fi.Stat()
		size = stat.Size()
		sk, err := fi.Seek(size, 0)
		if err != nil {
			log.Println(fn, "SEEK文件错误：", err)
			_ = fi.Close()
			onError(err)
			return
		}
		if sk != size {
			log.Printf("%s SEEK长度不等于文件长度："+
				"seek=%d,size=%d\n", fn, sk, size)
			_ = fi.Close()
			onError(err)
			return
		}
		file = fi
	} else {
		create, err := os.Create(dfn)
		if err != nil {
			log.Println(fn, "创建文件错误:", err)
			onError(err)
			return
		}
		file = create
	}
	client := &http.Client{}
	client.Timeout = to
	request := http.Request{}
	request.Method = http.MethodGet
	if size != 0 {
		header := http.Header{}
		header.Set("Range", "bytes="+strconv.FormatInt(size, 10)+"-")
		request.Header = header
	}
	parse, err := url.Parse(ur)
	if err != nil {
		log.Println(ur, "URL地址错误：", err)
		onError(err)
		return
	}
	request.URL = parse
	get, err := client.Do(&request)
	if err != nil {
		log.Println(ur, "访问URL错误：", err)
		onError(err)
		return
	}
	defer func() {
		err := get.Body.Close()
		if err != nil {
			log.Println(fn, "关闭正文错误：", err.Error())
			onError(err)
		}
		err = file.Close()
		if err != nil {
			log.Println(fn, "文件关闭错误：", err.Error())
			onError(err)
		}
	}()
	if get.ContentLength == 0 {
		log.Println(fn, "已经下载完成")
		return
	}
	body := get.Body
	writer := bufio.NewWriter(file)
	bs := make([]byte, 10*1024*1024) //每次读取的最大字节数，不可为0
	for {
		var read int
		read, err = body.Read(bs)
		if err != nil {
			if err != io.EOF {
				log.Println(fn, "读错误："+err.Error())
				onError(err)
			} else {
				err = nil
			}
			break
		}
		_, err = writer.Write(bs[:read])
		if err != nil {
			log.Println(fn, "写错误："+err.Error())
			onError(err)
			break
		}
	}
	if err != nil {
		return
	}
	err = writer.Flush()
	if err != nil {
		log.Println(fn, "FLUSH错误:", err.Error())
		onError(err)
		return
	}
	log.Println(fn, "下载成功")
}

func CheckToken(val string, tk string) bool {
	str := TKSTR + val
	md := md5.New()
	md.Write([]byte(str))
	str = hex.EncodeToString(md.Sum(nil))
	return tk == str
}
